#include <time.h>
#include <windows.h>
#include "pThreads/pthread.h"
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"
#include "math.h"

#include "CActionHandler.h"
#include "CActionPacket.h"
#include "CUtil.h"
#include "CMessagePacket.h"
#include "CMessagePacketTwo.h"
#include "CLockOnPacket.h"
#include "CLuaManager.h"
#include "CSpell.h"
#include "CWeaponItem.h"
#include "CRaiseTractorMenuPacket.h"
#include "CAbility.h"

DWORD WINAPI CharCastServ(LPVOID lpParam);
DWORD WINAPI CharRangeServ(LPVOID lpParam);

CActionHandler::CActionHandler(void) {
}

CActionHandler::~CActionHandler(void) {
}

int CActionHandler::genActionPacket(CChar *schar, unsigned char * data, char * in, unsigned short num) {
	unsigned long size = 0;
	//CNpc * ptrNpc;
	int npcid;
	unsigned int charid = schar->getId();

	char buffer[255];
	memset(buffer,0,255);

	memcpy(&npcid, in+4, 4);
	
	CConsole::outDebOnly("CLIENT PERFORMING ACTION 0x%02hX", in[0x0a]);
	switch((unsigned short)in[0x0a]) {
	
		case 0x00: { //trigger
			if(schar->m_pTarget == NULL) {
				CConsole::outErr("No Target!");
				break;
			}
			CLuaManager::OnTrigger(schar, schar->m_pTarget);
			if(schar->inEvent == false) {
				schar->release();
			}
			break;
		}

		case 0x02: { //attack

			if (schar->m_pTarget != NULL) {
				if((schar->m_pTarget->getObjectType() == OBJ_NPC ) || (schar->m_pTarget->isPlayer())) {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 446));
					break;
				}
				if(schar->isCasting() || schar->isRangedAttacking() || (schar->m_isUsingItem != 0)) {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
					break;
				}
				CNpc * tmpMob = (CNpc*)schar->m_pTarget;
				CChar * tmpChar = CChar::getCChar(tmpMob->ownerId, "", 0);

				if((tmpMob->ownerId == schar->getId()) || (tmpMob->ownerId == 0) || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
					schar->inBattle = true;
					schar->combatAction = 1;  // Normal attack
					CreateThread(0,0,CharBattleServ,schar,0,0);
				} else {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 0x0c));
				}
			}
			break;
		}
		case 0x03: //spellcast
			{
			if (schar->isRangedAttacking() || (schar->m_isUsingItem != 0)) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
				break;
			}
			unsigned short spellID;
			unsigned int targetID;
			memcpy(&spellID, in+0x0c, 2);
			memcpy(&targetID, in+0x04, 4);

			CSpell * spell = new CSpell(spellID);
			spell->setCaster(schar);
			spell->setSpellTargetID(targetID);
			

			CConsole::outDebOnly("SpellID   -> %i", spell->getSpellID());
			CConsole::outDebOnly("SpellName -> %s", spell->getSpellName());


			// get the target (i hate this way of retrieving them)
			CBaseEntity * tmp = CChar::getCChar(spell->getSpellTargetID(),"",0);

			if(tmp == NULL) {
				tmp = CZoneHandler::getInstance()->getNpc(spell->getSpellTargetID(),schar->getZone());
			}
			if(tmp == NULL) {
				CChar * tmpChar = NULL;
				tmp = CZoneHandler::getInstance()->getMob(spell->getSpellTargetID(),schar->getZone());
				if(tmp != NULL) {
					tmpChar = CChar::getCChar(((CNpc*)tmp)->ownerId, "", 0);
					if(((unsigned int)((CNpc*)tmp)->ownerId == schar->getId()) || (((CNpc*)tmp)->ownerId == 0) || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
					} else {
						schar->quePacket(CMessagePacket(schar, schar, 0, 0, 0x0c));
						break;
					}
				}
			}

			if(tmp == NULL) {
				break;
			}

			
			spell->setMainTarget(tmp);
			spell->setCastOrigin(spell->getCaster()->getPos());

			// check if the target is valid to the spell
			if(!spell->isValidTarget(tmp)) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 155));
				break;
			}

			// check if the caster is already casting another spell
			if(schar->isCasting()) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 17));
				break;
			}

			// check if the target is out of range
			if(CUtil::distance(schar->getPos(),tmp->getPos()) > 21) {
				schar->quePacket(CMessagePacket(schar, tmp, 0, 0, 4));
				break;
			}

			// check mana
			if(schar->getMp() < spell->getMPCost()) {
				schar->quePacket(CMessagePacket(schar, schar, spell->getSpellID(), 0, 34));
				break;
			}

			// check if the caster has a cooldown on the spell
			if(schar->hasSpellCooldown(spell->getSpellID())) {
				schar->quePacket(CMessagePacket(schar, schar, spell->getSpellID(), 0, 17));
				break;
			}

			// check if char has a pet and spell is summon
			if( (spell->getSpellSkill() == 38) && (schar->m_pPet != NULL) ) {
				schar->quePacket(CMessagePacket(schar, schar, spell->getSpellID(), 0, 47));
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 315));
				break;
			}

			// push the main target onto the targetlist
			spell->addTarget(spell->getMainTarget());
			spell->startCast();
			// start a thread for the casting process
			CreateThread(0,0,CharCastServ,spell,0,0);
			
			break;
		}
		case 0x04: //disengage
			schar->inBattle = false;
			schar->disengage();
			break;

		case 0x07: {//weaponskill
			if (schar->isCasting() || schar->isRangedAttacking() || (schar->m_isUsingItem != 0)) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
				break;
			}
			unsigned int targetID;
			memcpy(&targetID, in+0x04, 4);
			CNpc * tmpMob = CZoneHandler::getInstance()->getMob(targetID,schar->getZone());
			if (tmpMob != NULL) {
				//if((tmpMob->getObjectType() == OBJ_NPC) || (schar->m_pTarget->isPlayer())) {
				//	break;
				//}

				//CNpc * tmpMob = (CNpc*)schar->m_pTarget;
				CChar * tmpChar = CChar::getCChar(tmpMob->ownerId, "", 0);

				if(tmpMob->isDead()) {
					break;
				}

				if(CUtil::distance(tmpMob->loc.p, schar->loc.p)-tmpMob->sizeMod > 5) {
					schar->quePacket(CMessagePacket(schar, tmpMob, 0, 0, 0x04));
					break;
				}

				if(!schar->isFaceing(tmpMob->loc.p)) {
					schar->quePacket(CMessagePacket(schar, tmpMob, 0x00, 0x00, 0x05));
					break;
				}

				if((tmpMob->ownerId == schar->getId()) || (tmpMob->ownerId == 0) || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
					if(tmpMob->ownerId == 0) {
						tmpMob->ownerId = schar->getId();
					}
				} else {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 0x0c));
					break;
				}

				unsigned short weaponskill = 0;
				memcpy(&weaponskill, in + 0x0c, 1);

				CConsole::outTime("Using Weaponskill: %s ", CUtil::getWeaponSkillName(weaponskill));
				schar->getWeaponDamage(SLOT_MAIN);

				CBaseEntity* target = tmpMob;

				int damage = CLuaManager::OnUseWeaponSkill(schar, target, weaponskill);
				if (damage < 0) {
					damage = 0;
				}

				CWeaponItem * weapon = schar->getWeaponItem(SLOT_MAIN);
				if (weaponskill >= 192) 
					CWeaponItem * weapon = schar->getWeaponItem(SLOT_RANGED);
				
				damage = target->adjustDamage(weapon,damage);
				
				target->takePhysicalDamage(damage, schar);
				schar->weaponskill(weaponskill, damage, target);
				if(target->getObjectType() == OBJ_MOB && target->isDead()) {
					((CNpc*)target)->m_timeOfKill = (unsigned int)time(NULL) + 1;
				}
			}
			break;
		}

		case 0x09: //jobability

			{
			if (schar->isCasting() || schar->isRangedAttacking() || (schar->m_isUsingItem != 0)) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
				break;
			}
			unsigned short abilityID = 0;
			unsigned int targetID = 0;
			memcpy(&abilityID, in + 0x0c, 1);
			memcpy(&targetID,  in + 0x04, 4);

			CConsole::outTime("AbilityId %i",abilityID);
			CAbility * abi = new CAbility(abilityID);
			abi->setAbilityTargetID(targetID);
			abi->setUser(schar);
			CConsole::outTime("Using %s ",abi->getAbilityName());


				// get the target (i hate this way of retrieving them)
			CBaseEntity * tmp = CChar::getCChar(abi->getAbilityTargetID(),"",0);

			if(tmp == NULL) {
				tmp = CZoneHandler::getInstance()->getNpc(abi->getAbilityTargetID(),schar->getZone());
			}
			if(tmp == NULL) {
				CChar * tmpChar = NULL;
				tmp = CZoneHandler::getInstance()->getMob(abi->getAbilityTargetID(),schar->getZone());
				if(tmp != NULL) {
					tmpChar = CChar::getCChar(((CNpc*)tmp)->ownerId, "", 0);
					if(((unsigned int)((CNpc*)tmp)->ownerId == schar->getId()) || (((CNpc*)tmp)->ownerId == 0) || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
					} else {
						schar->quePacket(CMessagePacket(schar, schar, 0, 0, 0x0c));
						break;
					}
				}
			}

			if(tmp == NULL) {
				delete abi;
				break;
			}

			
			abi->setMainTarget(tmp);

			if(!abi->isValidTarget(tmp)) {
				delete abi;
				break;
			}

			if(CUtil::distance(tmp->loc.p, schar->loc.p) > abi->getAbilityRange()) {
				schar->quePacket(CMessagePacket(schar, tmp, 0, 0, 0x04));
				break;
			}


			// check if the caster has a cooldown on the spell
			if(schar->hasAbilityCooldown(abi->getAbilityRecastID())) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
				break;
			}
			
		
			abi->performAbility();
			

			delete abi;

			}
			break;

		case 0x0b: // homepoint
			if(	schar->m_hasTractor == false) {
				
			
                schar->h.hp = schar->getMaxHp();
                schar->h.hpp = 100;
                schar->h.mp = schar->getMaxMp();
                schar->h.mpp = 100;
                schar->updateChar();
                //Actually go to HP -- use failsafe in case someone doesn't have scripts.
                schar->zoning = 1;
                schar->onTransport = 0;
                position hp;
                if (schar->st.homepoint == 0) {
					hp.x = 0;
					hp.y = 0;
					hp.z = 0;
					hp.rotation = 0;
					schar->st.homepoint = 0xF5;
					schar->zoneChar(schar->st.homepoint,hp);
				} else {
					schar->zoneChar(schar->st.homepoint,schar->m_homePoint); 
				}
				schar->setAnimation(0x00);
				break;
			} else {
				schar->m_hasTractor = false;
				schar->quePacket(CRaiseTractorMenuPacket(schar, 0));
			}

			break;

		case 0x0c:  //assist
			break;

		case 0x0d:  //raise menu
			if(in[12] == 0) {
				
				schar->setAnimation(0x00);
				
				CActionPacket* raisePacket=new CActionPacket();
	
				raisePacket->setActorId(schar->getId());
				raisePacket->setCategorie(4);
				raisePacket->setStartAnimationCategorie(0x0000);
				raisePacket->setStartAnimation(0);
				raisePacket->setParameter1(0x0);
				raisePacket->setUnknown(0x0);
		
				apActionEntry newEntry;
				memset(&newEntry,0,sizeof(newEntry));

				newEntry.unknownValue = 0x00;
				
				newEntry.targetAnimationID = 0x1ff;

                // Get the right animation.
                switch(schar->m_hasRaise) {
					case 1:
						newEntry.targetAnimationID = 0x1ff;
						break;
					case 2:
						newEntry.targetAnimationID = 0x200;
						break;
					case 3:
						newEntry.targetAnimationID = 0x1f0;
						break;
				}
				newEntry.unknownValue2 = 0x022;

				raisePacket->setTargetID(1,schar->getId());
				raisePacket->setActionEntry(1,1,&newEntry);

				raisePacket->finalizePacket();

				schar->insertPacket(CHAR_INRANGE_SELF,*raisePacket);

				schar->h.hp = schar->getMaxHp();
				schar->h.hpp = 100;

				// determin how much hp/exp to recover upon raise
				switch(schar->m_hasRaise) {
					case 1:
						schar->gainExp((int)(schar->m_lostExp*0.5f));
						schar->h.hp = (int)((schar->getMaxHp())*0.10f);
						break;
					case 2:
						if(schar->m_lvlMain <= 50) {
							schar->gainExp((int)(schar->m_lostExp*0.5f));
						} else {
							schar->gainExp((int)(schar->m_lostExp*0.75f));
						}
						schar->h.hp = (int)((schar->getMaxHp())*0.25f);
						break;
					case 3:
						if(schar->m_lvlMain <= 50) {
							schar->gainExp((int)(schar->m_lostExp*0.5f));
						} else {
							schar->gainExp((int)(schar->m_lostExp*0.9f));
						}
						schar->h.hp = (int)((schar->getMaxHp())*0.50f);
						break;
				}

				schar->updateHealth();
				schar->updateChar();

				schar->m_hasRaise = 0;
				schar->m_lostExp = 0;

			} else {
				schar->m_hasRaise = 0;
			}

			break;

		case 0x10: //rangedattack
			//Can't do it twice!
			if (schar->isRangedAttacking() == true) {
				schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
				break;
			}

			if (schar->m_pTarget != NULL) {
				
				CNpc * tmpMob = (CNpc*)schar->m_pTarget;
				CChar * tmpChar = CChar::getCChar(tmpMob->ownerId, "", 0);

				//Can't attack during /heal
				if (schar->isHealing() == true) {
					schar->setAnimation(0x21);
					break;
				}
				//Proper weapon.
				int weapon = schar->getWeaponType(SLOT_RANGED);
				int ammo = schar->getWeaponType(SLOT_AMMO);
				if(!((weapon == 25 && ammo == 25) || (weapon == 26 && ammo == 26) || weapon == 27 || ammo == 27)) {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 216));
					break;
				}

				//Proper target.
				if((schar->m_pTarget->getObjectType() == OBJ_NPC ) || (schar->m_pTarget->isPlayer())) {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 446));
					break;
				}

				//Can't stop casting or using item for this (?)
				if(schar->isCasting() || (schar->m_isUsingItem != 0)) {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 94));
					break;
				}

				//Maximum distance of 25, according to FFXIclopedia
				if(CUtil::distance(tmpMob->loc.p, schar->loc.p) >= 25) {
					schar->quePacket(CMessagePacket(schar, tmpMob, 0, 0, 0x04));
					break;
				}

				//Heading check.
				if(!(schar->isFaceing(tmpMob->loc.p))) {
					schar->quePacket(CMessagePacket(schar, tmpMob, 0x00, 0x00, 0x05));
					break;
				}

				if((tmpMob->ownerId == schar->getId()) || (tmpMob->ownerId == 0) || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
					
					//Start ranged attack.
					CActionPacket* attackPacket=new CActionPacket();

					attackPacket->setActorId(schar->m_id);
					attackPacket->setCategorie(12); 
					attackPacket->setStartAnimationCategorie(0x18);
					attackPacket->setStartAnimation(0x676c);
					attackPacket->setParameter1(0x163);

					apActionEntry newEntry;
					memset(&newEntry,0,sizeof(newEntry));

					attackPacket->setTargetID(1,schar->m_id);
					attackPacket->setActionEntry(1,1,&newEntry);

					attackPacket->finalizePacket();
					attackPacket->savePacket();

					CChar::insertPacketGlob(CHAR_INRANGE,*attackPacket,schar);

					schar->m_isRangedAttacking = true;

					//Now go to CharRangeServ
					CreateThread(0,0,CharRangeServ,schar,0,0);
				} else {
					schar->quePacket(CMessagePacket(schar, schar, 0, 0, 0x0c));
				}
			}
			break;

		case 0x12:	//dismount
				schar->setAnimation(0x00);
				schar->updateChar();
			break;

		case 0x13: // tractor menu
			if(in[12] == 0) {
				schar->zoneChar((unsigned char)schar->getZone(),schar->m_tractorPos);
			} else {
				schar->m_hasTractor = false;
			}
			break;

		case 0x14:

				
			break;

		case 0x16:

			break;

	}
	
	return size;
}

DWORD WINAPI CharCastServ(LPVOID lpParam) {

	CSpell * spell = (CSpell*) lpParam;
	CChar * schar = (CChar*)spell->getCaster();
	long stamp = clock();

	while(clock() < (stamp + spell->getCastTime() * 1000)) {

		if(schar->isHealing()) {
			spell->interrupt();
			schar->setAnimation(0x21);
			schar->updateChar();
			return 0;
		}

		if((schar->isDead() || (spell->getMainTarget()->isDead() && !(spell->getAllowedTargets() & TARGET_PLAYER_DEAD)))) {
			spell->interrupt();
			schar->updateChar();
			return 0;
		}


		//Nothing is going to change within 200 milliseconds
		// save the CPU from spiking to 100%
		Sleep(200);
	}


	if(((spell->getCastOrigin().x != spell->getCaster()->getXPos()) || (spell->getCastOrigin().z != spell->getCaster()->getZPos()))) {
		
		spell->interrupt();
		schar->quePacket(CMessagePacket(schar, schar, 0, 0, 16));

		return 0;
	}

	// build final targetlist
	if( spell->isAOESpell() ) {
		// if spell has AOE effect, push those in range onto the targetlist
		if(spell->getMainTarget()->isMob()) {
			for(unsigned int i = 0; i < CZoneHandler::getInstance()->mobZoneList[schar->getZone()].size(); i++) {
				// check if the target is in range (15 atm) and if it's not the main-target
				if((CUtil::distance(CZoneHandler::getInstance()->mobZoneList[schar->getZone()].at(i)->loc.p,spell->getMainTarget()->loc.p) <= 10) 
				&& CZoneHandler::getInstance()->mobZoneList[schar->getZone()].at(i)->getId() != spell->getMainTarget()->getId()){
					//Also check if it's not claimed outside of the caster's alliance
					unsigned int ownerId = CZoneHandler::getInstance()->mobZoneList[schar->getZone()].at(i)->ownerId;
					CChar * tmpChar = schar->getCChar(ownerId,"",0);
					if(ownerId == schar->getId() || ownerId == 0 || (tmpChar != NULL && schar->isPartyMember(tmpChar))) {
						// push the target onto the targetlist
						if(!CZoneHandler::getInstance()->mobZoneList[schar->getZone()].at(i)->isDead()) {
							spell->addTarget((CBaseEntity*)CZoneHandler::getInstance()->mobZoneList[schar->getZone()].at(i));
						}
					}
				}
			}
		}
		if(spell->getMainTarget()->isPlayer()) {
			std::vector<CChar*> tmpList;
			tmpList = schar->getPartyMembersInRange(spell->getMainTarget()->getPos(), 15);
			for(unsigned int i = 0; i < tmpList.size(); i++) {
				if( !tmpList.at(i)->isDead() && (tmpList.at(i)->getId() != spell->getMainTarget()->getId())) {
					spell->addTarget(tmpList.at(i));
				}
					
			}
		}
	}
		
	

	// will be moved to a single function. setSpellCooldown() ?
	struct spellRecast rc;
	rc.spellID = spell->getSpellID();
	rc.timeStamp = (int)time(NULL);
	rc.recast = spell->getRecastTime();

	schar->lockRecastSpellList();
	schar->recastSpellList.push_back(rc);
	schar->unlockRecastSpellList();

	CBaseEntity * target = spell->getMainTarget();
	
	int result = 0;
	
	CActionPacket* actionPacket		=	new CActionPacket();
	actionPacket->setActorId(schar->getId());
	actionPacket->setCategorie(4);//Magic - End
	actionPacket->setParameter1(spell->getSpellID());
	actionPacket->setUnknown(0x50000000);

	apActionEntry newEntry;
	
	for(unsigned int i = 0; i < spell->m_targetList.size(); i++) {
			
		spell->setCurrentTargetNum(i+1);

		memset(&newEntry,0,sizeof(apActionEntry));
		
		if(!spell->isTeleportSpell()) {
			spell->setSpellTargetID(spell->m_targetList.at(i)->getId());
			result = CLuaManager::OnSpellCast(schar, spell);
		} else {
			if (i == 0) {
				spell->setSpellDefaultMsgType(93);
            } else {
				spell->setSpellDefaultMsgType(273);
			}
		}
		if (i < 16) {
			newEntry.targetAnimationID	=	spell->getSpellAnimID();
			newEntry.parameter2			=	result;
			newEntry.messageType		=	spell->getSpellDefaultMsgType();

			actionPacket->setTargetID(i+1,spell->m_targetList.at(i)->getId());
			actionPacket->setActionEntry(i+1,1,&newEntry);
		
		}

		spell->m_targetList.at(i)->takePhysicalDamage(0,schar);
	
		
	}

	actionPacket->finalizePacket();
	//actionPacket->savePacket();
	
    schar->insertPacket(CHAR_INRANGE_SELF, *actionPacket);

	CLuaManager::OnSpellCastFinish((CChar*)spell->getCaster(), spell);

	unsigned char skillID = spell->getSpellSkill();
	unsigned short maxSkill = schar->getMaxSkill(skillID);
	schar->doSkillUp(skillID, maxSkill);

	if(spell->isTeleportSpell()) {
		while(clock() < ((stamp + spell->getCastTime() * 1000) + 4000));
		spell->getCaster()->m_isCasting = false;
		schar->updateHealth();
		for(unsigned int i = 0; i < spell->m_targetList.size(); i++) {
			spell->setCurrentTargetNum(i+1);
			spell->setSpellTargetID(spell->m_targetList.at(i)->getId());
			CLuaManager::OnSpellCast(schar,spell);
		}
	}
	
	schar->updateHealth();
	
	Sleep(3000);
	spell->getCaster()->m_isCasting = false;

	return 1;

}



DWORD WINAPI CharRangeServ(LPVOID lpParam) {

	CChar * schar = (CChar*)lpParam;
	CNpc * tmpMob = (CNpc*)schar->m_pTarget;

	CWeaponItem * weapon = schar->getWeaponItem(SLOT_RANGED);
	CWeaponItem * ammo = schar->getWeaponItem(SLOT_AMMO);

	long stamp = clock();
	int delayweapon = schar->getDelay(SLOT_RANGED);
	int delayammo = schar->getDelay(SLOT_AMMO);
	position origin = schar->loc.p;

	while(clock() < (stamp + (delayweapon*1000/110))) {
		//Try to interrupt, etc.  Especially check for two actions at once, since that's an easy crash.

		//Can't be dead yourself.
		if (schar->isDead()) {
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Can't unequip or change during the shot.
		if(!(schar->getWeaponItem(SLOT_RANGED) == weapon && schar->getWeaponItem(SLOT_AMMO) == ammo)) {
			schar->quePacket(CMessagePacket(schar, schar, 0, 0, 216));
			schar->m_isRangedAttacking = false;
			return 0;
		}
		
		//Healing kills the thread.
		if (schar->isHealing()) {
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Casting kills the thread too, or at least this should prevent a double action crash.
		if (schar->isCasting()) {
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Moving interrupts your shot.
		if((schar->loc.p.x != origin.x) || (schar->loc.p.z != origin.z)) {
			schar->quePacket(CMessagePacket(schar, schar, 0, 0, 218));
			schar->m_isRangedAttacking = false;
			return 0;
		}
		
		//Maximum distance of 25, according to FFXIclopedia
		if(CUtil::distance(tmpMob->loc.p, schar->loc.p) >= 25) {
			schar->quePacket(CMessagePacket(schar, tmpMob, 0, 0, 0x04));
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Heading check.
		if(!(schar->isFaceing(tmpMob->loc.p))){
			schar->quePacket(CMessagePacket(schar, tmpMob, 0x00, 0x00, 36));
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Can't shoot a dead man.
		if(tmpMob->isDead()) {
			schar->quePacket(CMessagePacket(schar, tmpMob, 0x00, 0x00, 446));
			schar->m_isRangedAttacking = false;
			return 0;
		}

		//Prevent CPU spike.
		Sleep(200);
	}
	
	int damage = 0;

	CActionPacket* attackPacket=new CActionPacket();
	
	attackPacket->setActorId(schar->m_id);
	attackPacket->setCategorie(2); 
	attackPacket->setStartAnimationCategorie(0x1a);
	attackPacket->setStartAnimation(0x676c);
	attackPacket->setParameter1(0x0073);

	apActionEntry newEntry;
	memset(&newEntry,0,sizeof(newEntry));

	short hitrate = 75;
	if (ammo != 0) hitrate = schar->getHitRate(tmpMob,SLOT_AMMO);
	if (weapon != 0) hitrate = schar->getHitRate(tmpMob,SLOT_RANGED);

	srand(clock());
	if(rand()%100 < hitrate) { //Successful hit
	
		//Calculate damage -- thanks kegsay!
		int weapondmg = 0;
		int ammodmg = 0;
		if (weapon != 0) weapondmg = weapon->getDamage();
		if (ammo != 0) ammodmg = ammo->getDamage();

		int base = (weapondmg + ammodmg);
		int fstr = schar->getFStrTwo(tmpMob);
	
		//Skilltype to use
		double distance = CUtil::distance(tmpMob->loc.p, schar->loc.p);
		double dstpenalty = 1;
		int skill = 0;

		switch (weapon->getSkillType()){
			case 25: { //Archery
				skill = schar->s.archery;
				dstpenalty -= abs(0.05*(8 - distance));
				break;
			}
			case 26: { //Marksmanship -- should be different for gun.
				skill = schar->s.marksmanship;
				dstpenalty -= abs(0.05*(9 - distance));
				break;
			}
			case 27: { //Throwing -- Weapon only
				skill = schar->s.throwing;
				distance -= abs(0.05*(1 - distance));
				break;
			}
		}

		//RAtk; no gear buffs
		double ratk = dstpenalty*(8 + (skill/10) + schar->stats.str + schar->stats.pStr);

		//cRatio
		double cRatio = ratk/tmpMob->getDef();
		if (tmpMob->m_lvlMain > schar->m_lvlMain) {
			cRatio -= (0.025 * (tmpMob->m_lvlMain - schar->m_lvlMain));
		}
	
		if (cRatio > 3) {
			cRatio = 3;
		} else if (cRatio < 0) {
			cRatio = 0;
		}

		double minPDif = 1;
		double maxPDif = 1;

		if (cRatio < 0.9) {
			minPDif = cRatio;
			maxPDif = 10*cRatio / 9;
		} else if (cRatio > 1.1) {
			minPDif = (1 + (20*(cRatio-1.1)/19));
			maxPDif = cRatio;
		}

		if (minPDif < 0) {
			minPDif = 0;
		}
		if (maxPDif < minPDif) {
			maxPDif = minPDif+0.01;
		}

		newEntry.messageType = 352;
		float pDif = CUtil::randFloat((float)minPDif, (float)maxPDif);
		if((unsigned)rand()%100 < schar->getCritHitRateOn(tmpMob)) { //Critical hit!
			pDif *= 1.25;
			newEntry.unknownValue2=0x044;
			newEntry.messageType=353;
		}

		//Final damage
		damage = (int) ((base+fstr)* pDif);
		//Adjust for weapon type
		if (weapon != NULL) {
			damage = tmpMob->adjustDamage(weapon,damage);
		} else {
			damage = tmpMob->adjustDamage(ammo,damage);
		}
		newEntry.parameter2=damage;
		

		if(tmpMob->getObjectType() == OBJ_MOB && tmpMob->isDead()) {
			((CNpc*)tmpMob)->m_timeOfKill = (unsigned int)time(NULL)+1;
		}

		//Gain TP:
		float tpperhit = 0;

		if(damage > 0) {

			int delay = delayweapon+delayammo;
			
			if (delay <= 180){
				tpperhit = (((delay-180)*1.5f)/180)+5;
			}
			else if (delay > 180 && delay <= 450){
				tpperhit = (((delay-180)*6.5f)/270)+5;
			}
			else if (delay > 450 && delay <= 480){
				tpperhit = (((delay-450)*1.5f)/30)+11.5f;
			}
			else if (delay > 480 && delay <= 530){
				tpperhit = (((delay-480)*1.5f)/50)+13;
			}
			else if (delay > 530 && delay <= 999){
				tpperhit = (((delay-530)*3.5f)/470)+14.5f;
			}
			else{
				printf ("Error with delay. Value: %i", tpperhit);
			}
		}

		if(schar->h.tp + tpperhit < 300) {	
			schar->h.tp += (int)tpperhit;
		} else {
			schar->h.tp = 300;
		}

		//Give the enemy TP
		if(tmpMob->h.tp + tpperhit < 300) {	
			tmpMob->h.tp += (short)(tpperhit/3);
		} else {
			tmpMob->h.tp = 300;
		}

		schar->updateHealth();

		//Try to skill up!
		unsigned char skillID = schar->getWeaponType(SLOT_RANGED);
		unsigned short maxSkill = schar->getMaxSkill(skillID);
		schar->doSkillUp(skillID, maxSkill);

	} else { //Missed
		newEntry.unknownValue = 0x11; 
		newEntry.messageType = 0x162;
	}

	//Finalize and send the packet.
	attackPacket->setTargetID(1,tmpMob->m_id);
	attackPacket->setActionEntry(1,1,&newEntry);
	attackPacket->finalizePacket();
	attackPacket->savePacket();
	CChar::insertPacketGlob(CHAR_INRANGE,*attackPacket,schar);

	//Consume ammo...
	if (ammo != NULL) {
		schar->removeItemQuantity(ammo->getId(),LOC_INVENTORY,1);
	}

	//Do it!
	tmpMob->takePhysicalDamage(damage, schar);

	//End the shot.
	schar->m_isRangedAttacking = false;
	return 1;

}
